
/********************************** pwm driver *******************************/
#include "pwm.h"
#if USE_PWM
#include <stdio.h>
#include <stdint.h>
#include "../../board.h"

/* PWM registers and bits definitions */
#define PWM_BASE                        0xD401A000
#define PWMCR                           0x00
#define PWMDCR                          0x04
#define PWMPCR                          0x08
#define PWMCR_SD                        (1 << 6)
#define PWMDCR_FD                       (1 << 10)

/* apb clk */
#define APBCLK_PWM_BASE                 0xD401500C
#define FCLK                            (1 << 1)
#define APBCLK                          (1 << 0)
#define RESET                           (1 << 2)

/* using 13M function clk */
#define PERIOD_FACTOR_FOR_13M           769

static uint8_t pwm_apb_clk[PWM_DEV_MAX];

static void
apbclk_disable(void)
{
  unsigned val;

  if((pwm_apb_clk[0] == 0) && (pwm_apb_clk[1] == 0)) {
    val = readl(APBCLK_PWM_BASE);
    if(val & APBCLK) {
      printf("apb clk 0 disable\n");
      writel(RESET, APBCLK_PWM_BASE);
    }
  }

  if((pwm_apb_clk[2] == 0) && (pwm_apb_clk[3] == 0)) {
    val = readl(APBCLK_PWM_BASE + 0x8);
    if(val & APBCLK) {
      printf("apb clk 2 disable\n");
      writel(RESET, APBCLK_PWM_BASE + 0x8);
    }
  }
}
static void
apbclk_enable(void)
{
  unsigned val;

  if((pwm_apb_clk[0] == 1) || (pwm_apb_clk[1] == 1)) {
    val = readl(APBCLK_PWM_BASE);
    if((val & APBCLK) == 0) {
      printf("apb clk 0 enable\n");
      writel((val | APBCLK) & (~RESET), APBCLK_PWM_BASE);
    }
  }

  if((pwm_apb_clk[2] == 1) || (pwm_apb_clk[3] == 1)) {
    val = readl(APBCLK_PWM_BASE + 0x8);
    if((val & APBCLK) == 0) {
      printf("apb clk 2 enable\n");
      writel((val | APBCLK) & (~RESET), APBCLK_PWM_BASE + 0x8);
    }
  }
}
static void
clk_enable(int id)
{
  pwm_apb_clk[id] = 1;
  apbclk_enable();
  writel(FCLK | APBCLK, APBCLK_PWM_BASE + 0x4 * id);
}
static void
clk_disable(int id)
{
  unsigned val;

  pwm_apb_clk[id] = 0;
  val = readl(APBCLK_PWM_BASE + 0x4 * id);
  writel(val & ~FCLK, APBCLK_PWM_BASE + 0x4 * id);
  apbclk_disable();
}
int
lv_pwm_enable(pwm_dev_t id, int duty_ns, int period_ns)
{
  unsigned period_cycles, prescale, pv, dc;
  unsigned reg_base;

  if(id >= PWM_DEV_MAX) {
    printf("pwm_enable: invalid id%d\n", id);
    return -1;
  }
  reg_base = PWM_BASE + 0x400 * id;
  if(duty_ns == 0) {
    writel(0x0, reg_base + PWMDCR);
    return 0;
  }

  period_cycles = (period_ns * 10) / PERIOD_FACTOR_FOR_13M;

  if(period_cycles < 1) {
    period_cycles = 1;
  }
  prescale = (period_cycles - 1) / 1024;
  pv = period_cycles / (prescale + 1) - 1;

  if(prescale > 63) {
    printf("pwm_enable: invalid prescale %u\n", prescale);
    return -1;
  }

  if(duty_ns == period_ns) {
    dc = PWMDCR_FD;
  } else {
    dc = (pv + 1) * duty_ns / period_ns;
  }

  clk_enable(id);

  writel(prescale, reg_base + PWMCR);
  writel(dc, reg_base + PWMDCR);
  writel(pv, reg_base + PWMPCR);

  return 0;
}
void
lv_pwm_disable(pwm_dev_t id)
{
  if(id >= PWM_DEV_MAX) {
    printf("pwm_disable: invalid id%d\n", id);
    return;
  }
  lv_pwm_enable(id, 0, 0);
  clk_disable(id);
}
#endif
